package cn.wch.meshlib.core;

import android.annotation.SuppressLint;
import android.app.Application;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.os.Handler;
import android.os.Looper;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Locale;

import cn.wch.blelib.utils.LogUtil;
import cn.wch.meshlib.constant.UUIDConstant;
import cn.wch.meshlib.frame.CommandGenerateUtil;
import cn.wch.meshlib.frame.ack.AckFrame;
import cn.wch.meshlib.frame.ack.CommandAckUtil;
import cn.wch.meshlib.ota.ChipType;
import cn.wch.meshlib.ota.FileParseUtil;
import cn.wch.meshlib.ota.IOTAUpdateProgress;
import cn.wch.meshlib.ota.ImageInfo;
import cn.wch.meshlib.util.CalculateUtil;

public class MeshServer extends AbstractMeshController{

    protected BluetoothGattCharacteristic mWrite;
    protected BluetoothGattCharacteristic mNotify;

    protected Handler handler=new Handler(Looper.getMainLooper());

    private static volatile MeshServer meshServer;
    /**闪存地址基数*/
    private final static int ADDRESS_BASE=8;
    /**帧头*/
    private final static int FRAME_HEAD=5;
    /**bin类型升级文件擦除的开始地址*/
    private static final int START_ADDRESS_BIN=0x1000;

    private boolean stopFlag=false;
    private IOTAUpdateProgress progress;

    public MeshServer() {
      super();
    }
    /**
     * 获取MeshServer实例
     * @return
     */
    public static MeshServer getInstance() {
        if(meshServer==null){
            synchronized (MeshServer.class){
                meshServer=new MeshServer();
            }
        }
        return meshServer;
    }

    @SuppressLint("NewApi")
    @Override
    boolean checkCharacteristics(List<BluetoothGattService> list) {
        mWrite=null;
        mNotify=null;
        for (BluetoothGattService service : list) {
            if (service.getUuid().toString().equalsIgnoreCase(UUIDConstant.ServiceUUID)) {
                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                    String s = characteristic.getUuid().toString();
                    if (s.equalsIgnoreCase(UUIDConstant.WriteCharacterUUID)) {
                        mWrite=characteristic;
                        mWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

                    }else if(s.equalsIgnoreCase(UUIDConstant.NotifyCharacterUUID)){
                        mNotify=characteristic;
                    }
                }
            }
        }
        return mWrite!=null  && mNotify!=null;
    }

    @Override
    void preSet(String mac) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                openNotify(mNotify);
                setMTU();
            }
        });
    }

//    /**
//     * 设置特征变化通知回调
//     * @param notifyDataCallback
//     * @return
//     */
//    public boolean setNotifyListener( NotifyDataCallback notifyDataCallback){
//        if(mNotify!=null && notifyDataCallback!=null){
//            return this.setNotifyListener(mNotify,notifyDataCallback);
//        }
//        return false;
//    }
    public void init(Application application) throws Exception {
        initApplication(application);
    }

    /**
     * 获取网络信息
     * @return
     * @throws Exception
     */
    public AckFrame getNetworkInfo() throws Exception {
        byte[] provisionCommand = CommandGenerateUtil.getProvisionInfoCommand((byte)0,null,(byte) 0);
        byte[] response = writeAndRead(mWrite, provisionCommand, provisionCommand.length);
        return CommandAckUtil.parseAck(response);
    }

    //ivIndex小端
    /**
     * 设置网络信息
     * @param ivIndex
     * @param flag
     * @return
     * @throws Exception
     */
    public AckFrame setNetworkInfo(int ivIndex, byte flag) throws Exception {
        byte[] provisionCommand = CommandGenerateUtil.getProvisionInfoCommand((byte) 0x01,
                CalculateUtil.int2ByteArrayLittleEndian(ivIndex,2),flag);
        byte[] response = writeAndRead(mWrite, provisionCommand, provisionCommand.length);
        return CommandAckUtil.parseAck(response);
    }

    /**
     * 节点配置
     * @param key 要配置是密钥
     * @param nodeAddress 节点地址
     * @return
     * @throws Exception
     */
    //address must <=7fff
    public AckFrame meshProvision(byte[] key,int nodeAddress) throws Exception {
        if(nodeAddress<0){
            throw new Exception("invalid address");
        }
        if(nodeAddress>=0x00007fff){
            throw new Exception("invalid address,cannot be greater than 0x7FFF");
        }
        byte[] provisionCommand = CommandGenerateUtil.getProvisionCommand
                (key,CalculateUtil.int2ByteArrayLittleEndian(nodeAddress,2));
        byte[] response = writeAndRead(mWrite, provisionCommand, provisionCommand.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        return ackFrame;
    }

    /**
     * 删除节点
     * @param nodeAddress 节点地址
     * @return
     * @throws Exception
     */
    public AckFrame deleteNode(int nodeAddress) throws Exception {
        byte[] deleteNodeCommand = CommandGenerateUtil.getDeleteNodeCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress,2));
        byte[] response = writeAndRead(mWrite, deleteNodeCommand, deleteNodeCommand.length);
        return CommandAckUtil.parseAck(response);
    }

    /**
     * 查询节点状态
     * @param nodeAddress 节点地址
     * @return
     * @throws Exception
     */
    public AckFrame queryNodeState(int nodeAddress) throws Exception {
        byte[] queryNodeStateCommand = CommandGenerateUtil.getQueryNodeStateCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress,2));
        byte[] response = writeAndRead(mWrite, queryNodeStateCommand, queryNodeStateCommand.length);
        return CommandAckUtil.parseAck(response);
    }

    /**
     * 传输数据（向Mesh设备发送数据的函数）
     * @param address 节点/组地址
     * @param data 传输的数据
     * @return
     * @throws Exception
     */
    public boolean transferData(int address, byte[] data) throws Exception {
        byte[] transferDataCommand = CommandGenerateUtil.getTransferDataCommand(
                CalculateUtil.int2ByteArrayLittleEndian(address,2),data);
        return write(mWrite,transferDataCommand,transferDataCommand.length);
    }

    /**
     * ota节点图像信息查询
     * @param address 目标节点地址
     * @return
     * @throws Exception
     */
    public ImageInfo otaQueryNodeImageInfo(int address) throws Exception {
        byte[] command = CommandGenerateUtil.getOTAQueryNodeImageCommand(
                CalculateUtil.int2ByteArrayLittleEndian(address,2));
        byte[] response = writeAndRead(mWrite, command, command.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        if(ackFrame==null){
            throw new Exception("invalid ack");
        }
        return ackFrame.toImageInfo();
    }

    /**
     * 开始OTA更新
     * @param nodeAddress 节点地址
     * @param imageInfo 图像信息
     * @param file 文件
     * @param progress 进度
     * @throws Exception
     */
    public void startOTAUpdate(int nodeAddress, ImageInfo imageInfo, File file, IOTAUpdateProgress progress)throws Exception{
        stopFlag=false;
        this.progress=progress;
        if(imageInfo.getChipType()== ChipType.UNKNOWN){
            throw new IOException("unknown chip type");
        }
        LogUtil.d("current chip type:"+imageInfo.getChipType().toString());
        int startAddr=0;
        ByteBuffer byteBuffer=null;
        LogUtil.d("start update use file: "+file.getAbsolutePath());

        //升级地址
        if(FileParseUtil.isBinFile(file)){
            startAddr=START_ADDRESS_BIN;//固定值
        }else if(FileParseUtil.isHexFile(file)){
            startAddr=FileParseUtil.getHexFileEraseAddr(file);//从十六进制文件解析
        }
        LogUtil.d(String.format(Locale.getDefault(),"start address 0x%x", startAddr));
        if(progress!=null){
            progress.onInformation("start address "+String.format(Locale.getDefault(),"0x%X", startAddr));
        }
        //从文件读取数据
        if(file.getName().endsWith(".bin") || file.getName().endsWith(".BIN")){
            byteBuffer= FileParseUtil.parseBinFile(file,startAddr);
        }else if(file.getName().endsWith(".hex") || file.getName().endsWith(".HEX")){
            byteBuffer= FileParseUtil.parseHexFile(file);
        }else {
            throw new Exception("only support hex and bin image file");
        }
        if(byteBuffer==null){
            throw new Exception("parse file fail");
        }
        int total=  byteBuffer.capacity();
        LogUtil.d("total size: "+total);
        LogUtil.d("image info: "+imageInfo.toString());
        //检查不合格
        if(imageInfo.getImageSize()<total){
            throw new Exception("image size is less than file content length");
        }
        //最大数据传输长度
        int mtu = getMTU();
        int frameMaxLen = getSpecificMaxPacketForOTAAlign(mtu);
        int dataMaxLen = frameMaxLen - FRAME_HEAD;
        LogUtil.d("mtu: "+mtu+" frameMaxLen:"+frameMaxLen+" dataMaxLen:"+dataMaxLen);
        //数据缓存
        byte[] realBuffer = byteBuffer.array();
        //开始升级
        if(progress!=null){
            progress.onUpdateStart();
        }
        int offset=0;

        while (offset<realBuffer.length){
            if(checkStopFlag()){
                return;
            }
            //有效数据的长度
            int dataLen = getRealDataLen(dataMaxLen,realBuffer, offset);
            LogUtil.d("ready send data length:"+dataLen);
            if (!otaUpdate(nodeAddress,startAddr/ADDRESS_BASE+offset/ADDRESS_BASE,realBuffer,offset,dataLen)) {
                if(progress!=null){
                    progress.onError("update fail!");
                }
                return;
            }
            offset+=dataLen;
            LogUtil.d("update progress: "+offset+"/"+realBuffer.length);
            if(progress!=null){
                progress.onUpdateProgress(offset,realBuffer.length);
            }
        }
        if(progress!=null){
            progress.onUpdateFinish();
        }
        //开始验证
        if(progress!=null){
            progress.onVerifyStart();
        }
        offset=0;
        while (offset<realBuffer.length){
            if(checkStopFlag()){
                return;
            }
            //有效数据的长度
            int dataLen = getRealDataLen(dataMaxLen,realBuffer, offset);
            if (!otaVerify(nodeAddress,startAddr/ADDRESS_BASE+offset/ADDRESS_BASE,realBuffer,offset,dataLen)) {
                if(progress!=null){
                    progress.onError("verify fail!");
                }
                return;
            }
            offset+=dataLen;
            LogUtil.d("verify progress: "+offset+"/"+realBuffer.length);
            if(progress!=null){
                progress.onVerifyProgress(offset,realBuffer.length);
            }
        }
        if(progress!=null){
            progress.onVerifyFinish();
        }
        //
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //发送结束命令
        if(otaEnd(nodeAddress)){
            LogUtil.d("#otaEnd");
            if(progress!=null){
                progress.onEnd();
            }
        }else {
            if(progress!=null){
                progress.onError("end fail!");
            }
        }
    }

    /**
     * 取消OTA更新
     */
    public void cancelOTAUpdate(){
        stopFlag=true;
    }

    /**
     * OTA升级
     * @param nodeAddress 节点地址
     * @param flashAddress 组地址
     * @param data 升级数据
     * @param offset 偏移
     * @param length 长度
     * @return
     * @throws Exception
     */
    private boolean otaUpdate(int nodeAddress,int flashAddress,byte[] data,int offset,int length) throws Exception {
        byte[] command = CommandGenerateUtil.getOTAUpdateCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress, 2),
                flashAddress, data,offset,length);
        byte[] response = writeAndRead(mWrite, command, command.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        if(ackFrame==null){
            throw new Exception("invalid ack");
        }
        if(ackFrame.getStatusCode()==0 && ackFrame.getNetAddress()==nodeAddress && flashAddress==ackFrame.getUpdateAddress()){
            return true;
        }
        return false;
    }

    /**
     * OTA验证
     * @param nodeAddress 节点地址
     * @param flashAddress 组地址
     * @param data 验证数据
     * @param offset 偏移量
     * @param length 长度
     * @return
     * @throws Exception
     */
    private boolean otaVerify(int nodeAddress,int flashAddress,byte[] data,int offset,int length) throws Exception {
        byte[] command = CommandGenerateUtil.getOTAVerifyCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress, 2), flashAddress,  data,offset,length);
        byte[] response = writeAndRead(mWrite, command, command.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        if(ackFrame==null){
            throw new Exception("invalid ack");
        }
        if(ackFrame.getStatusCode()==0 && ackFrame.getNetAddress()==nodeAddress && flashAddress==ackFrame.getUpdateAddress()){
            return true;
        }
        return false;
    }

    /**
     * OTA结束。升级完成后发送
     * @param nodeAddress 节点地址
     * @return
     * @throws Exception
     */
    private boolean otaEnd(int nodeAddress) throws Exception {
        byte[] command = CommandGenerateUtil.getOTAEndCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress, 2));
        return write(mWrite, command, command.length);
    }

    /**
     * 订阅
     * @param nodeAddress 节点地址
     * @param groupAddress 组地址
     * @return
     * @throws Exception
     */
    public boolean subscribe(int nodeAddress,int groupAddress)throws Exception{
        //获取订阅命令
        byte[] command =CommandGenerateUtil.getSubscribeNodeCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress, 2),
                (byte) 1,CalculateUtil.int2ByteArrayLittleEndian(groupAddress, 2));
        //写特征数据并读出
        byte[] response = writeAndRead(mWrite, command, command.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        if(ackFrame==null){
            throw new Exception("invalid ack");
        }
        if(ackFrame.getStatusCode()==0 && ackFrame.getNetAddress()==nodeAddress){
            return true;
        }
        return false;
    }

    /**
     * 取消订阅
     * @param nodeAddress 节点地址
     * @param groupAddress 组地址
     * @return
     * @throws Exception
     */
    public boolean cancelSubscribe(int nodeAddress,int groupAddress)throws Exception{
        byte[] command =CommandGenerateUtil.getSubscribeNodeCommand(
                CalculateUtil.int2ByteArrayLittleEndian(nodeAddress, 2),
                (byte) 0,CalculateUtil.int2ByteArrayLittleEndian(groupAddress, 2));
        byte[] response = writeAndRead(mWrite, command, command.length);
        AckFrame ackFrame = CommandAckUtil.parseAck(response);
        if(ackFrame==null){
            throw new Exception("invalid ack");
        }
        if(ackFrame.getStatusCode()==0 && ackFrame.getNetAddress()==nodeAddress){
            return true;
        }
        return false;
    }

    public AckFrame reset() throws Exception {
        byte[] resetCommand = CommandGenerateUtil.getResetCommand();
        byte[] response = writeAndRead(mWrite, resetCommand, resetCommand.length);
        return CommandAckUtil.parseAck(response);
    }

    //OTA字节对齐问题,数据域为8的整数倍,mesh的最大包长不能超过221
    private int getSpecificMaxPacketForOTAAlign(int mtu)throws Exception{
        int frameMaxLen = mtu - 3;
        int times = (frameMaxLen - FRAME_HEAD)/ADDRESS_BASE;
        for (int i = times; i > 0; i--) {
            int tempDataLen = i * ADDRESS_BASE;
            int tempFrameLen = tempDataLen + FRAME_HEAD;
            if(tempFrameLen<=221){
                return tempFrameLen;
            }
        }
        throw new Exception("Unexpected MTU!");
    }

    /**
     * 获取实际应该发送数据的长度
     * @param maxDataLen
     * @param dataSource
     * @param offset
     * @return
     */
    private int getRealDataLen(int maxDataLen,byte[] dataSource,int offset){
        return Math.min(maxDataLen,dataSource.length-offset);
    }

    private boolean checkStopFlag(){
        if(stopFlag){
            if(progress!=null){
                progress.onCancel();
            }
            return true;
        }
        return false;
    }

}
